Skip to content

Conversation

erikhfj
Copy link
Member

@erikhfj erikhfj commented Sep 25, 2026

What this does

Adds the CI workflow for #27. NTNU's GitHub Enterprise has no hosted runners, so both jobs run on the self-hosted runner on the group VM. A second runner, it2810-19-project2, is registered to this repo alongside the existing Project 1 one.

Jobs

quality runs on every PR and every push to main:

pnpm install --frozen-lockfile
pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm build
client bundle budget

deploy runs on push to main only, after quality passes. Publishes client/dist to /var/www/html/project2 and curls the site to confirm it served. It restarts crate-api only if that systemd unit exists, so the deploy works today at M1 and still works when the backend lands in M2.

What was done

  • .github/workflows/ci.yml with the two jobs above
  • Registered it2810-19-project2 as a self-hosted runner on the VM (separate from the Project 1 runner, which is repo-scoped to T19-Project-1)
  • Concurrency group so a new push to the same ref cancels the in-flight run
  • actions/checkout plus pnpm/action-setup and setup-node with pnpm caching

What was tested

  • Runner registered and reachable: gh api repos/.../actions/runners reports it2810-19-project2 with labels self-hosted, Linux, X64, project2
  • Local equivalents of every quality step pass on the feat/1-repo-tooling branch: format:check, lint with 0 warnings, typecheck, test (3 tests), build
  • Deploy steps are the same commands used to put the current prototype on the VM, plus a curl assertion

The runner is not yet running as a service, so CI will show it offline until that is installed. That is one sudo command and is called out in the PR comments.

Notes on choices

  • Bundle budget at 512 kB. Loose on purpose for now (the skeleton is 220 kB). The point is that CI fails before a dependency quietly doubles our payload. One of our documented bærekraft measures.
  • deploy skips crate-api if absent rather than failing. Otherwise every push to main would be red until M2, which trains people to ignore CI.
  • Separate runner from Project 1. The P1 runner is registered to T19-Project-1 and will not pick up this repo's jobs. Keeping them separate means a long P1 build cannot block us.

Closes

Closes #27.

@erikhfj erikhfj requested a review from a team as a code owner September 25, 2026 19:09
@erikhfj erikhfj force-pushed the chore/27-ci-workflow branch from dfbd714 to 8e2dadb Compare September 25, 2026 19:18
@erikhfj erikhfj changed the base branch from main to feat/1-repo-tooling September 25, 2026 19:18
@erikhfj
Copy link
Member Author

erikhfj commented Sep 25, 2026

Note on the two runs above:

Run 1 (failed, 12s): actions/setup-node with cache: pnpm aborted because pnpm-lock.yaml does not exist on a branch that only contains ci.yml. Fair enough, a workflow cannot quality-gate a repo that has no project in it yet.

Fix: this PR is now stacked on feat/1-repo-tooling (#32) rather than on main. The diff you see here is only .github/workflows/ci.yml; the tooling commits are the base. Run 2 is green.

After #32 merges, retarget this PR to main and it will stay green:

gh pr edit 34 --base main

Or merge #32 first and reopen this against main. Either way the workflow content does not change.

@rachelks
Copy link
Member

You mention that the new runner has the project2 label, but I noticed both jobs use only [self-hosted, linux, x64]. Would it make sense to include project2 in runs-on as well, especially for the deploy job, to make sure it runs on the intended runner?

erikhfj and others added 2 commits September 28, 2026 10:49
Two jobs, both on [self-hosted, linux, x64] since NTNU GitHub Enterprise
has no hosted runners.

quality (every PR and every push to main):
  pnpm install --frozen-lockfile, then format:check, lint, typecheck,
  test, build, and a client bundle budget gate at 512 kB per JS chunk.

deploy (push to main only):
  builds with the production settings and publishes client/dist to
  /var/www/html/project2, then curls the site to confirm it served.
  Restarts crate-api only if the unit exists, so the deploy works today
  at M1 and still works when the backend lands in M2.

The bundle budget is a bærekraft measure: it is deliberately loose now
and tightens as the app grows, so a dependency cannot quietly double our
payload without CI noticing.

Co-Authored-By: opencode <noreply@opencode.ai>
Found in review of #34: both jobs used [self-hosted, linux, x64] even
though the runner is labelled project2. That works today because the
Project 2 runner is repo-scoped and is the only one that can pick up
these jobs, but it is not what the labels are for.

The two jobs now differ deliberately:

- quality is unpinned beyond the platform. It is a pure function of the
  checked-out tree, so it can run on any self-hosted runner we add later
  without editing this file.

- deploy is pinned to [self-hosted, linux, x64, project2]. It writes to
  /var/www/html/project2 and restarts crate-api, both of which only
  exist on it2810-19. If a second runner is ever registered to this
  repo from a different machine, this job must not land there.

Reported by rachelks.

Co-Authored-By: opencode <noreply@opencode.ai>
@erikhfj erikhfj force-pushed the chore/27-ci-workflow branch from 8e2dadb to 6ab73b6 Compare September 28, 2026 16:51
@erikhfj
Copy link
Member Author

erikhfj commented Sep 28, 2026

You are right, and the label was doing no work as written. Fixed.

The two jobs now differ deliberately:

  • quality stays [self-hosted, linux, x64]. It is a pure function of the checked-out tree, so it can run on any self-hosted runner we add later without editing this file. If someone ever registers a second runner to speed up CI, quality can use it.

  • deploy is now [self-hosted, linux, x64, project2]. This is the one where it actually matters: the job writes to /var/www/html/project2 and restarts crate-api, both of which only exist on it2810-19. If a second runner is ever registered to this repo from a different machine, this job must not land there.

Today it happened to be safe because the Project 1 runner is repo-scoped to T19-Project-1 and cannot pick up our jobs at all. That is an accident of registration, not something the workflow should depend on.

So your instinct about pinning the deploy job specifically was the right half of it. I left quality unpinned on purpose, and said so in a comment in the workflow so the next person does not "helpfully" add the label to both.

Found by actually verifying rather than assuming: PR #34 targets
feat/1-repo-tooling because it is stacked on #32, and the workflow's
`pull_request: branches: [main]` filter silently skipped CI for it. The
push of 6ab73b6 produced no run at all.

Stacked PRs are how we are working (see docs/WORKFLOW.md), so a branch
filter on pull_request is wrong. pull_request now has no filter and runs
for every PR whatever it targets; push stays limited to main so we only
deploy from the mainline.

Co-Authored-By: opencode <noreply@opencode.ai>
Copy link
Member

@rachelks rachelks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed after the changes. The runner distinction makes sense to me, and all checks pass locally as well. Looks good!

@rachelks rachelks merged commit 0dde36c into feat/1-repo-tooling Sep 28, 2026
2 checks passed
erikhfj added a commit that referenced this pull request Sep 28, 2026
The workflow is on main for the first time. PR #34 merged into
feat/1-repo-tooling (its base at the time) after #32 had already merged
that branch into main, so the workflow never reached main and the repo
has been running with no CI at all. This brings it across.

Content is unchanged from what #34 reviewed and what has been passing on
the runner: quality job (format, lint, typecheck, test, build, bundle
budget) on every PR and every push to main, deploy job on push to main
only.

Two fixes that came out of actually running it:

- pull_request has no branch filter. A `branches: [main]` filter meant
  stacked PRs like #34, which targets feat/1-repo-tooling, silently got
  zero CI runs.

- deploy is pinned to [self-hosted, linux, x64, project2]. It writes to
  /var/www/html/project2 and restarts crate-api, which only exist on
  it2810-19. quality stays unpinned so any future runner can pick it up.

Co-Authored-By: opencode <noreply@opencode.ai>
Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CI/CD pipeline on the self-hosted runner
2 participants